using System;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Collections.Specialized;
using System.IO;
using System.Xml;
using System.Net.Sockets;
using System.Threading;

namespace natol.GoogleReaderAPI
{
    public class GoogleReader
    {
        #region Instance Variables and properties
        public string UserID;
        private WebProxy _proxy;
        private Uri _proxyUri;
        private ICredentials _creds;
        private bool _DoubleSlashOnUri;
        private bool _CheckedCompat = false;
        private bool DoubleSlashOnUri
        {
            get
            {
                if (_CheckedCompat == false)
                    _DoubleSlashOnUri = IsUriCompat();
                return _DoubleSlashOnUri;
            }
            set { _DoubleSlashOnUri = value; }
        }
        private Dictionary<string, string> Cookies = new Dictionary<string, string>();
        private Dictionary<string, int> _unreadCounts;
        public Dictionary<string, int> UnreadCounts
        {
            get { return _unreadCounts; }
            set { _unreadCounts = value; }
        }
        private string _GUserName;
        public string GUserName
        {
            get { return _GUserName; }
            set { _GUserName = value; }
        }
        private string _GPassword;
        public string GPassword
        {
            get { return _GPassword; }
            set { _GPassword = value; }
        }
        #endregion Instance Variables and properties

        #region Constructors
        public GoogleReader()
        {
            Connect();
        }
        #endregion Constructors

        #region Private Helper Methods
        private bool IsUriCompat()
        {
            _CheckedCompat = true;
            string testUrl = "http://www.test.com/feed/http://www.test2.com";
            Uri testUri = new Uri(testUrl);
            if (testUrl == testUri.AbsoluteUri)
                return true;
            else
                return false;
        }
        private bool VerifyUserString(string UIDs)
        {
            bool success = true;
            int i = UIDs.Length;
            while (i > 0)
            {
                try
                {
                    Convert.ToInt32(UIDs.Substring(i - 1, 1));
                }
                catch
                {
                    success = false;
                    break;
                }
                finally
                {
                    i--;
                }

            }
            return success;
        }
        #region Network Methods
        private string GetSource(string URL)
        {
            return GetSource(URL, false);
        }
        private string GetSource(string URL, bool AllowRedirect)
        {
            StreamReader reader = new StreamReader(GetResponseStream(URL, AllowRedirect), Encoding.UTF8);
            return reader.ReadToEnd();
        }
        private string GetSource(WebRequest request)
        {
            StreamReader sr = new StreamReader(GetResponseStream(request), Encoding.UTF8);
            string result = sr
                .ReadToEnd();
            sr.Close();
            return result;
        }
        private XmlDocument GetXmlDoc(string URL)
        {
            XmlDocument result = new XmlDocument();
            result.Load(GetResponseStream(URL, false));
            return result;
        }
        private WebRequest GetRequest(string URL)
        {
            return GetRequest(URL, false);
        }
        private WebRequest GetRequest(string URL, bool AllowRedirect)
        {
            HttpWebRequest webRequest2 = (HttpWebRequest)WebRequest.Create(URL);

            //save proxy details for later
            if (webRequest2.Proxy.GetProxy(new Uri(URL)) != null)
                _proxyUri = webRequest2.Proxy.GetProxy(new Uri(URL));
            if (webRequest2.Proxy.Credentials != null)
                _creds = webRequest2.Proxy.Credentials;
            webRequest2 = LoadCookies(webRequest2);
            webRequest2.Accept = "*/*";
            webRequest2.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705)";
            webRequest2.AllowAutoRedirect = AllowRedirect;
            return webRequest2;
        }
        private string GetCookieString()
        {
            string requestCookies = "";
            foreach (KeyValuePair<string, string> cookie in Cookies)
            {
                requestCookies = string.Format("{0}{1}={2};", requestCookies, cookie.Key, cookie.Value);
            }
            return requestCookies;
        }
        public Stream GetResponseStream(string URL, bool AllowRedirect)
        {
            return GetResponseStream(GetRequest(URL, AllowRedirect));
        }
        public Stream GetResponseStream(WebRequest request)
        {
            HttpWebResponse webResponse2 = (HttpWebResponse)request.GetResponse();
            SaveCookies(webResponse2);
            return webResponse2.GetResponseStream();
        }
        private void SaveCookies(WebResponse response)
        {
            string cookiesRaw = response.Headers["Set-Cookie"];
            if (!string.IsNullOrEmpty(cookiesRaw))
            {
                string[] cookiesList = cookiesRaw.Split(';');
                foreach (string cookieRaw in cookiesList)
                {
                    //split the cookie in to key and value
                    string[] cookieComponents = cookieRaw.Split('=');
                    if (cookieComponents.Length == 2)
                    {
                        string cookieKey = cookieComponents[0];
                        string cookieValue = cookieComponents[1];
                        if (!Cookies.ContainsKey(cookieKey))
                            Cookies.Add(cookieKey, cookieValue);
                        else
                            Cookies[cookieKey] = cookieValue;
                    }
                }
            }
        }
        private HttpWebRequest LoadCookies(HttpWebRequest request)
        {
            if (Cookies.Count > 0)
            {
                string requestCookies = "";
                if (!string.IsNullOrEmpty(request.Headers["Cookie"]))
                    requestCookies = request.Headers["Cookie"];
                requestCookies += GetCookieString();
                request.Headers["Cookie"] = requestCookies;
            }
            return request;
        }
        #endregion Network Methods
        #region Google XML Processing
        private GFeedItems BuildItems(XmlNodeList sublist)
        {
            GFeedItems feeditems = new GFeedItems();
            foreach (XmlNode theno in sublist)
            {
                GFeedItem feeditem = new GFeedItem();
                foreach (XmlNode subno in theno.ChildNodes)
                {
                    switch (subno.Name)
                    {
                        case "title":
                            feeditem.Title = subno.InnerText;
                            break;
                        case "summary":
                            feeditem.Description += subno.InnerText;
                            break;
                        case "content":
                            feeditem.Description += subno.InnerText;
                            break;
                        case "published":
                            feeditem.Published = Convert.ToDateTime(subno.InnerText);
                            break;
                        case "id":
                            feeditem.ID = subno.InnerText;
                            break;
                        case "link":
                            if (subno.Attributes["href"] != null)
                            {
                                feeditem.Url = subno.Attributes["href"].Value;
                            }
                            break;
                        case "category":
                            if (subno.Attributes["label"] != null)
                            {
                                switch (subno.Attributes["label"].Value)
                                {
                                    case "read":
                                        feeditem.Read = true;
                                        break;
                                    case "kept-unread":
                                        feeditem.Read = false;
                                        break;
                                }
                            }
                            else
                            {
                                string[] tags = subno.Attributes["term"].InnerText.Split(',');
                                foreach (string tag in tags)
                                {
                                    if (feeditem.Tags.BinarySearch(tag) < 0)
                                        feeditem.Tags.Add(tag);

                                }
                            }

                            break;
                    }
                }
                feeditems.Add(feeditem);
            }
            return feeditems;
        }
                #endregion Google XML Processing
        #endregion Private Helper Methods

        #region Google Reader Specific Methods
        public bool IsConnected()
        {
            if (!string.IsNullOrEmpty(UserID))
                return VerifyUserString(UserID);
            else
                return false;
        }
        public bool Connect()
        {
            if (string.IsNullOrEmpty(_GUserName) || string.IsNullOrEmpty(_GPassword))
                return false;
            //authenticate and set cookie
            GetSource("https://www.google.com/accounts/ServiceLoginAuth?continue=http%3A%2F%2Fwww.google.com%2Freader&service=reader&nui=1&Email=" + _GUserName + "&Passwd=" + _GPassword + "&PersistentCookie=yes&rmShown=1&null=Sign+in");
            //get user id
            string source = GetSource("http://www.google.com/reader/", true);
            int loc = source.IndexOf("_USER_ID = \"");
            string uid = source.Substring(loc + 12, 20);
            if (VerifyUserString(uid) == true)
            { UserID = uid; return true; }
            else { return false; }
        }
        public string GetToken()
        {
            return GetSource("http://www.google.com/reader/api/0/token");
        }
        public bool AddSubscription(string URL)
        {
            //Validate Url
            if (!URL.StartsWith("http://"))
                URL = string.Format("http://{0}", URL);
            Uri UrlValidator = new Uri(URL);
            WebRequest webRequest2 = GetRequest("http://www.google.com/reader/quickadd", true);
            webRequest2.ContentType = "application/x-www-form-urlencoded";
            webRequest2.Method = "POST";
            // Set values for the request back 
            ASCIIEncoding encoding = new ASCIIEncoding();
            string strNewValue = "quickadd=" + UrlValidator.Host + UrlValidator.PathAndQuery + "&T=" + GetToken();
            byte[] data = encoding.GetBytes(strNewValue);
            Stream stream = webRequest2.GetRequestStream();
            stream.Write(data, 0, data.Length);
            // Write the request 
            StreamWriter stOut = new StreamWriter(stream, encoding);
            stOut.Close();
            WebResponse webResponse2;
            //get response
            try
            {
                webResponse2 = webRequest2.GetResponse();
            }
            catch (WebException)
            {
                return false;
            }
            Stream resStream = webResponse2.GetResponseStream();
            StreamReader reader = new StreamReader(resStream, Encoding.ASCII);
            string result = reader.ReadToEnd();
            if (result.IndexOf("FR_QuickAdd_success") >= 0)
                return true;
            else
                return false;
        }
        public bool ToggleRead(GFeedItem item, bool MarkRead)
        {
            return ToggleRead(item.parentFeed.ID, item.Read, item.ID, MarkRead);
        }
        public bool ToggleRead(string FeedID, bool CurrentlyRead, string ItemID, bool MarkRead)
        {
            if (CurrentlyRead == MarkRead)
                return false;

            string postData = "s=" + FeedID +
                "&i=" + ItemID +
                "&ac=edit-tags" +
                "&a=user/" + UserID + "/state/com.google/" + ((!MarkRead) ? "kept-unread" : "read") +
                ((!MarkRead) ? "&r=user/" + UserID + "/state/com.google/read" : "") +
                "&T=" + GetToken();
            byte[] data = new ASCIIEncoding().GetBytes(postData);

            //prepare request
            WebRequest request = GetRequest("http://www.google.com/reader/api/0/edit-tag");
            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";
            request.ContentLength = data.Length;
            request.ConnectionGroupName = "Togglingreader" + Guid.NewGuid();
            Stream newStream = request.GetRequestStream();

            //send data
            newStream.Write(data, 0, data.Length);
            //newStream.Close();
            string result = "";
            try
            {
                result = GetSource(request);
            }
            catch (WebException ex)
            {
                result = "400";
            }
            finally
            {
                newStream.Close();
            }
            if (result == "OK")
                return true;
            else
                return false;
        }
        public GRootFolder GetFeedList()
        {
            Dictionary<string, GFeed> rootresults = new Dictionary<string, GFeed>();
            Dictionary<string, GFolder> Folders = new Dictionary<string, GFolder>();

            XmlDocument feedlist = GetXmlDoc("http://www.google.com/reader/api/0/subscription/list?output=xml");
            XmlNode sublist = feedlist.GetElementsByTagName("list")[0];

            foreach (XmlNode theno in sublist.ChildNodes)
            {
                if (theno.Name == "object")
                {
                    GFeed feed = new GFeed();
                    string folderid = string.Empty;
                    string id = "";
                    string title = "";
                    foreach (XmlNode feedNode in theno.ChildNodes)
                    {
                        //if string then get name
                        if (feedNode.Name == "string" & feedNode.Attributes["name"].InnerText == "id")
                        {
                            id = feedNode.InnerText;
                        }
                        //if number then get number
                        if (feedNode.Name == "string" & feedNode.Attributes["name"].InnerText == "title")
                        {
                            title = feedNode.InnerText;
                        }
                        if (feedNode.Name == "list" & feedNode.Attributes["name"].InnerText == "categories" & feedNode.ChildNodes.Count >= 1)
                        {
                            //found a directory, add it to the list of dirs if not already there
                            string foldername = "";
                            folderid = "";

                            XmlNode dirNode = feedNode.ChildNodes[0];
                            foreach (XmlNode dirMetaNode in dirNode.ChildNodes)
                            {
                                if (dirMetaNode.Name == "string")
                                {
                                    switch (dirMetaNode.Attributes["name"].InnerText)
                                    {
                                        case "id":
                                            folderid = dirMetaNode.InnerText;
                                            break;
                                        case "label":
                                            foldername = dirMetaNode.InnerText;
                                            break;
                                    }
                                }
                            }
                            if (Folders.ContainsKey(folderid) != true)
                            {
                                GFolder tmpfold = new GFolder();
                                tmpfold.FolderName = foldername;
                                tmpfold.GCategory = folderid;
                                Folders.Add(folderid, tmpfold);
                            }
                        }
                    }
                    feed.ID = id;
                    feed.Title = title;

                    if (folderid == string.Empty)
                        rootresults.Add(id, feed);
                    else
                        Folders[folderid].Feeds.Add(feed);
                }
            }

            //build structure
            GRootFolder GStructure = new GRootFolder();
            foreach (GFolder folder in Folders.Values)
            {
                GStructure.Folders.Add(folder);
            }
            foreach (GFeed tfeed in rootresults.Values)
            {
                GStructure.Feeds.Add(tfeed);
            }

            return GStructure;
        }
        public Dictionary<string, int> UpdateUnread()
        {
            UnreadCounts = GetUnread();
            return UnreadCounts;
        }
        public int GetUnreadCount(string FeedUrl)
        {
            int result = 0;
            try { result = UnreadCounts[FeedUrl]; }
            catch { }
            return result;
        }
        private Dictionary<string, int> GetUnread()
        {
            Dictionary<string, int> results = new Dictionary<string, int>();
            XmlDocument doc = new XmlDocument();
            doc.Load(GetResponseStream(
                "http://www.google.com/reader/api/0/unread-count?all=true&output=xml", false));
            XmlNode unreadlist = doc.GetElementsByTagName("list")[0];
            foreach (XmlNode theno in unreadlist.ChildNodes)
            {
                if (theno.Name == "object")
                {
                    int i = 0;
                    string name = "";
                    int number = new int();
                    //if string then get name
                    if (theno.ChildNodes[0].Name == "string")
                    {
                        name = theno.ChildNodes[0].InnerText;
                        number = Convert.ToInt32(theno.ChildNodes[1].InnerText);
                    }
                    //if number then get number
                    if (theno.ChildNodes[0].Name == "number")
                    {
                        number = Convert.ToInt32(theno.ChildNodes[0].InnerText);
                        name = theno.ChildNodes[1].InnerText;
                    }
                    results.Add(name, number);

                }
            }
            return results;

        }
        #region FeedRetreival
        public GFeed GetFeed(string FeedID)
        {
            return GetFeed(FeedID, null, 20);
        }
        public GFeed GetFeed(string FeedID, string Continuation, int Count)
        {
            if (DoubleSlashOnUri)
                return GetFeedByRequest(FeedID, Continuation, Count);
            else
                return GetFeedByTCP(FeedID, Continuation, Count);

        }
        private GFeed GetFeedByTCP(string FeedID, string Continuation, int Count)
        {
            System.Net.Sockets.TcpClient client = new System.Net.Sockets.TcpClient(
                ((_proxyUri == null) ? "www.google.com" : _proxyUri.Host),
                ((_proxyUri == null) ? 80 : _proxyUri.Port));

            NetworkStream clientSocketStream = client.GetStream();
            StreamReader clientStreamR = new StreamReader(clientSocketStream, Encoding.UTF8);
            StreamWriter clientStreamW = new StreamWriter(clientSocketStream);

            //write request
            clientStreamW.Write("GET http://www.google.com/reader/atom/" + FeedID + "?"
                + ((Continuation != null) ? "c=" + Continuation + "&" : "")
                + "r=n&n=" + Count.ToString() + " HTTP/1.1 \r\n");

            //write header
            clientStreamW.Write("Host: www.google.com\r\n");
            clientStreamW.Write(string.Format("Cookie: {0}\r\n", GetCookieString()));

            //write newline
            clientStreamW.Write("\r\n");
            clientStreamW.Flush();

            while (!clientSocketStream.DataAvailable)
            {
                System.Threading.Thread.Sleep(1000);
            }

            string status = clientStreamR.ReadLine();

            string result = "";

            if (status.IndexOf("200") == -1)
            {
                clientStreamR.Close();
                client.Close();
                throw new WebException("Did not get a 200 response");
            }

            //we are ok to continue
            bool reacheddoc = false;
            int ContentToRead = -1;
            while (reacheddoc == false)
            {
                while (!clientSocketStream.DataAvailable)
                {
                    System.Threading.Thread.Sleep(1000);
                }
                string line = clientStreamR.ReadLine();
                char[] splitter = { ':' };
                if (line == "")
                {
                    ContentToRead = int.Parse(clientStreamR.ReadLine(), System.Globalization.NumberStyles.HexNumber);
                    reacheddoc = true;
                }
            }
            bool reachedend = false;
            int ChunkSize = ContentToRead;
            while (reachedend == false)
            {
                //read a line
                string line = "";
                bool cr = false;
                bool lf = false;
                while (ContentToRead > 0)
                {
                    char[] fbtmp = new char[1];
                    clientStreamR.Read(fbtmp, 0, 1);
                    if (fbtmp[0] != '\r' & fbtmp[0] != '\n' & fbtmp[0] != '\t')
                    {
                        line += new string(fbtmp);
                        ContentToRead--;
                    }
                    else
                    {
                        if (ContentToRead >= 139)
                        {
                            line += new string(fbtmp);
                            ContentToRead--;
                        }
                        else
                        {
                            if (fbtmp[0] == '\r')
                            {
                                if (!cr)
                                    cr = true;
                            }
                            if (fbtmp[0] == '\n')
                            {
                                if (!cr)
                                {
                                    line += new string(fbtmp);
                                    ContentToRead--;
                                }
                                else
                                {
                                    lf = true;
                                    break;
                                }
                            }

                        }


                    }
                }
                char[] dbuff = new char[2];
                if (!lf) clientStreamR.Read(dbuff, 0, 2);

                result += line;

                //read chunk size
                cr = false;
                lf = false;
                int size = 0;
                // 8 hex digits should be enough
                for (int i = 0; i < 10; i++)
                {
                    char[] cbuff = new char[1];
                    clientStreamR.Read(cbuff, 0, 1);
                    char c = Char.ToUpper((char)cbuff[0]);
                    if (c == '\r')
                    {
                        if (!cr)
                        {
                            cr = true;
                            continue;
                        }
                        throw new IOException("Bad stream: 2 CR");
                    }

                    if (c == '\n' && cr == true)
                    {
                        if (!lf)
                        {
                            lf = true;
                            break;
                        }

                        throw new IOException("Bad stream: got LF but no CR");
                    }

                    if (i < 8 && ((c >= '0' && c <= '9') || c >= 'A' && c <= 'F'))
                    {
                        size = size << 4;
                        if (c >= 'A' && c <= 'F')
                            size += c - 'A' + 10;
                        else
                            size += c - '0';
                        continue;
                    }

                    throw new IOException("Bad stream: got " + c);
                }
                if (!cr || !lf)
                    throw new IOException("Bad stream: no CR or LF after chunk size");

                ContentToRead = size;
                ChunkSize = size;
                //end read chunk size

                if (ContentToRead == 0)
                    reachedend = true;
            }

            clientStreamR.Close();
            client.Close();

            XmlDocument doc = new XmlDocument();
            doc.LoadXml(result);
            GFeed feed = new GFeed();
            try { feed.Continuation = doc.GetElementsByTagName("gr:continuation")[0].InnerText; }
            catch { }
            try { feed.LastUpdated = Convert.ToDateTime(doc.GetElementsByTagName("updated")[0].InnerText); }
            catch (Exception) { }

            feed.Items = BuildItems(doc.GetElementsByTagName("entry"));
            feed.ID = FeedID;

            return feed;

        }
        private GFeed GetFeedByRequest(string FeedID, string Continuation, int Count)
        {
            GFeed feed = new GFeed();
            XmlDocument feedlist = GetXmlDoc(
                "http://www.google.com/reader/atom/" + FeedID + "?" +
                ((Continuation != null) ? "c=" + Continuation + "&" : "") +
                "r=n&n=" + Count.ToString());
            try { feed.Continuation = feedlist.GetElementsByTagName("gr:continuation")[0].InnerText; }
            catch { }
            try { feed.Title = feedlist.GetElementsByTagName("title")[0].InnerText; }
            catch { }
            feed.Items = BuildItems(feedlist.GetElementsByTagName("entry"));
            feed.ID = FeedID;

            return feed;
        }
        #endregion FeedRetreival
        #endregion Google Reader Specific Methods
    }
}
